/*
 * ! jQuery Form Plugin version: 3.51.0-2014.06.20 Requires jQuery v1.5 or later
 * Copyright (c) 2014 M. Alsup Examples and documentation at:
 * http://malsup.com/jquery/form/ Project repository:
 * https://github.com/malsup/form Dual licensed under the MIT and GPL licenses.
 * https://github.com/malsup/form#copyright-and-license
 */
/* global ActiveXObject */

// AMD support
(function(factory) {
	"use strict";
	if (typeof define === 'function' && define.amd) {
		// using AMD; register as anon module
		define(['jquery'], factory);
	} else {
		// no AMD; invoke directly
		factory((typeof(jQuery) != 'undefined') ? jQuery : window.Zepto);
	}
}

(function($) {
	"use strict";

	/*
	 * Usage Note: ----------- Do not use both ajaxSubmit and ajaxForm on the
	 * same form. These functions are mutually exclusive. Use ajaxSubmit if you
	 * want to bind your own submit handler to the form. For example,
	 * 
	 * $(document).ready(function() { $('#myForm').on('submit', function(e) {
	 * e.preventDefault(); // <-- important $(this).ajaxSubmit({ target:
	 * '#output' }); }); });
	 * 
	 * Use ajaxForm when you want the plugin to manage all the event binding for
	 * you. For example,
	 * 
	 * $(document).ready(function() { $('#myForm').ajaxForm({ target: '#output'
	 * }); });
	 * 
	 * You can also use ajaxForm with delegation (requires jQuery v1.7+), so the
	 * form does not have to exist when you invoke ajaxForm:
	 * 
	 * $('#myForm').ajaxForm({ delegation: true, target: '#output' });
	 * 
	 * When using ajaxForm, the ajaxSubmit function will be invoked for you at
	 * the appropriate time.
	 */

	/**
	 * Feature detection
	 */
	var feature = {};
	feature.fileapi = $("<input type='file'/>").get(0).files !== undefined;
	feature.formdata = window.FormData !== undefined;

	var hasProp = !!$.fn.prop;

	// attr2 uses prop when it can but checks the return type for
	// an expected string. this accounts for the case where a form
	// contains inputs with names like "action" or "method"; in those
	// cases "prop" returns the element
	$.fn.attr2 = function() {
		if (!hasProp) {
			return this.attr.apply(this, arguments);
		}
		var val = this.prop.apply(this, arguments);
		if ((val && val.jquery) || typeof val === 'string') {
			return val;
		}
		return this.attr.apply(this, arguments);
	};

	/**
	 * ajaxSubmit() provides a mechanism for immediately submitting an HTML form
	 * using AJAX.
	 */
	$.fn.ajaxSubmit = function(options) {
		/* jshint scripturl:true */

		// fast fail if nothing selected (http://dev.jquery.com/ticket/2752)
		if (!this.length) {
			log('ajaxSubmit: skipping submit process - no element selected');
			return this;
		}

		var method, action, url, $form = this;

		if (typeof options == 'function') {
			options = {
				success : options
			};
		} else if (options === undefined) {
			options = {};
		}

		method = options.type || this.attr2('method');
		action = options.url || this.attr2('action');

		url = (typeof action === 'string') ? $.trim(action) : '';
		url = url || window.location.href || '';
		if (url) {
			// clean url (don't include hash vaue)
			url = (url.match(/^([^#]+)/) || [])[1];
		}

		options = $.extend(true, {
					url : url,
					success : $.ajaxSettings.success,
					type : method || $.ajaxSettings.type,
					iframeSrc : /^https/i.test(window.location.href || '')
							? 'javascript:false'
							: 'about:blank'
				}, options);

		// hook for manipulating the form data before it is extracted;
		// convenient for use with rich editors like tinyMCE or FCKEditor
		var veto = {};
		this.trigger('form-pre-serialize', [this, options, veto]);
		if (veto.veto) {
			log('ajaxSubmit: submit vetoed via form-pre-serialize trigger');
			return this;
		}

		// provide opportunity to alter form data before it is serialized
		if (options.beforeSerialize
				&& options.beforeSerialize(this, options) === false) {
			log('ajaxSubmit: submit aborted via beforeSerialize callback');
			return this;
		}

		var traditional = options.traditional;
		if (traditional === undefined) {
			traditional = $.ajaxSettings.traditional;
		}

		var elements = [];
		var qx, a = this.formToArray(options.semantic, elements);
		if (options.data) {
			options.extraData = options.data;
			qx = $.param(options.data, traditional);
		}

		// give pre-submit callback an opportunity to abort the submit
		if (options.beforeSubmit
				&& options.beforeSubmit(a, this, options) === false) {
			log('ajaxSubmit: submit aborted via beforeSubmit callback');
			return this;
		}

		// fire vetoable 'validate' event
		this.trigger('form-submit-validate', [a, this, options, veto]);
		if (veto.veto) {
			log('ajaxSubmit: submit vetoed via form-submit-validate trigger');
			return this;
		}

		var q = $.param(a, traditional);
		if (qx) {
			q = (q ? (q + '&' + qx) : qx);
		}
		if (options.type.toUpperCase() == 'GET') {
			options.url += (options.url.indexOf('?') >= 0 ? '&' : '?') + q;
			options.data = null; // data is null for 'get'
		} else {
			options.data = q; // data is the query string for 'post'
		}

		var callbacks = [];
		if (options.resetForm) {
			callbacks.push(function() {
						$form.resetForm();
					});
		}
		if (options.clearForm) {
			callbacks.push(function() {
						$form.clearForm(options.includeHidden);
					});
		}

		// perform a load on the target only if dataType is not provided
		if (!options.dataType && options.target) {
			var oldSuccess = options.success || function() {
			};
			callbacks.push(function(data) {
						var fn = options.replaceTarget ? 'replaceWith' : 'html';
						$(options.target)[fn](data).each(oldSuccess, arguments);
					});
		} else if (options.success) {
			callbacks.push(options.success);
		}

		options.success = function(data, status, xhr) { // jQuery 1.4+ passes
														// xhr as 3rd arg
			var context = options.context || this; // jQuery 1.4+ supports
													// scope context
			for (var i = 0, max = callbacks.length; i < max; i++) {
				callbacks[i]
						.apply(context, [data, status, xhr || $form, $form]);
			}
		};

		if (options.error) {
			var oldError = options.error;
			options.error = function(xhr, status, error) {
				var context = options.context || this;
				oldError.apply(context, [xhr, status, error, $form]);
			};
		}

		if (options.complete) {
			var oldComplete = options.complete;
			options.complete = function(xhr, status) {
				var context = options.context || this;
				oldComplete.apply(context, [xhr, status, $form]);
			};
		}

		// are there files to upload?

		// [value] (issue #113), also see comment:
		// https://github.com/malsup/form/commit/588306aedba1de01388032d5f42a60159eea9228#commitcomment-2180219
		var fileInputs = $('input[type=file]:enabled', this).filter(function() {
					return $(this).val() !== '';
				});

		var hasFileInputs = fileInputs.length > 0;
		var mp = 'multipart/form-data';
		var multipart = ($form.attr('enctype') == mp || $form.attr('encoding') == mp);

		var fileAPI = feature.fileapi && feature.formdata;
		log("fileAPI :" + fileAPI);
		var shouldUseFrame = (hasFileInputs || multipart) && !fileAPI;

		var jqxhr;

		// options.iframe allows user to force iframe mode
		// 06-NOV-09: now defaulting to iframe mode if file input is detected
		if (options.iframe !== false && (options.iframe || shouldUseFrame)) {
			// hack to fix Safari hang (thanks to Tim Molendijk for this)
			// see:
			// http://groups.google.com/group/jquery-dev/browse_thread/thread/36395b7ab510dd5d
			if (options.closeKeepAlive) {
				$.get(options.closeKeepAlive, function() {
							jqxhr = fileUploadIframe(a);
						});
			} else {
				jqxhr = fileUploadIframe(a);
			}
		} else if ((hasFileInputs || multipart) && fileAPI) {
			jqxhr = fileUploadXhr(a);
		} else {
			jqxhr = $.ajax(options);
		}

		$form.removeData('jqxhr').data('jqxhr', jqxhr);

		// clear element array
		for (var k = 0; k < elements.length; k++) {
			elements[k] = null;
		}

		// fire 'notify' event
		this.trigger('form-submit-notify', [this, options]);
		return this;

		// utility fn for deep serialization
		function deepSerialize(extraData) {
			var serialized = $.param(extraData, options.traditional).split('&');
			var len = serialized.length;
			var result = [];
			var i, part;
			for (i = 0; i < len; i++) {
				// #252; undo param space replacement
				serialized[i] = serialized[i].replace(/\+/g, ' ');
				part = serialized[i].split('=');
				// #278; use array instead of object storage, favoring array
				// serializations
				result.push([decodeURIComponent(part[0]),
						decodeURIComponent(part[1])]);
			}
			return result;
		}

		// XMLHttpRequest Level 2 file uploads (big hat tip to francois2metz)
		function fileUploadXhr(a) {
			var formdata = new FormData();

			for (var i = 0; i < a.length; i++) {
				formdata.append(a[i].name, a[i].value);
			}

			if (options.extraData) {
				var serializedData = deepSerialize(options.extraData);
				for (i = 0; i < serializedData.length; i++) {
					if (serializedData[i]) {
						formdata.append(serializedData[i][0],
								serializedData[i][1]);
					}
				}
			}

			options.data = null;

			var s = $.extend(true, {}, $.ajaxSettings, options, {
						contentType : false,
						processData : false,
						cache : false,
						type : method || 'POST'
					});

			if (options.uploadProgress) {
				// workaround because jqXHR does not expose upload property
				s.xhr = function() {
					var xhr = $.ajaxSettings.xhr();
					if (xhr.upload) {
						xhr.upload.addEventListener('progress',
								function(event) {
									var percent = 0;
									var position = event.loaded
											|| event.position; /*
																 * event.position
																 * is deprecated
																 */
									var total = event.total;
									if (event.lengthComputable) {
										percent = Math.ceil(position / total
												* 100);
									}
									options.uploadProgress(event, position,
											total, percent);
								}, false);
					}
					return xhr;
				};
			}

			s.data = null;
			var beforeSend = s.beforeSend;
			s.beforeSend = function(xhr, o) {
				// Send FormData() provided by user
				if (options.formData) {
					o.data = options.formData;
				} else {
					o.data = formdata;
				}
				if (beforeSend) {
					beforeSend.call(this, xhr, o);
				}
			};
			return $.ajax(s);
		}

		// private function for handling file uploads (hat tip to YAHOO!)
		function fileUploadIframe(a) {
			var form = $form[0], el, i, s, g, id, $io, io, xhr, sub, n, timedOut, timeoutHandle;
			var deferred = $.Deferred();

			// #341
			deferred.abort = function(status) {
				xhr.abort(status);
			};

			if (a) {
				// ensure that every serialized input is still enabled
				for (i = 0; i < elements.length; i++) {
					el = $(elements[i]);
					if (hasProp) {
						el.prop('disabled', false);
					} else {
						el.removeAttr('disabled');
					}
				}
			}

			s = $.extend(true, {}, $.ajaxSettings, options);
			s.context = s.context || s;
			id = 'jqFormIO' + (new Date().getTime());
			if (s.iframeTarget) {
				$io = $(s.iframeTarget);
				n = $io.attr2('name');
				if (!n) {
					$io.attr2('name', id);
				} else {
					id = n;
				}
			} else {
				$io = $('<iframe name="' + id + '" src="' + s.iframeSrc
						+ '" />');
				$io.css({
							position : 'absolute',
							top : '-1000px',
							left : '-1000px'
						});
			}
			io = $io[0];

			xhr = { // mock object
				aborted : 0,
				responseText : null,
				responseXML : null,
				status : 0,
				statusText : 'n/a',
				getAllResponseHeaders : function() {
				},
				getResponseHeader : function() {
				},
				setRequestHeader : function() {
				},
				abort : function(status) {
					var e = (status === 'timeout' ? 'timeout' : 'aborted');
					log('aborting upload... ' + e);
					this.aborted = 1;

					try { // #214, #257
						if (io.contentWindow.document.execCommand) {
							io.contentWindow.document.execCommand('Stop');
						}
					} catch (ignore) {
					}

					$io.attr('src', s.iframeSrc); // abort op in progress
					xhr.error = e;
					if (s.error) {
						s.error.call(s.context, xhr, e, status);
					}
					if (g) {
						$.event.trigger("ajaxError", [xhr, s, e]);
					}
					if (s.complete) {
						s.complete.call(s.context, xhr, e);
					}
				}
			};

			g = s.global;
			// trigger ajax global events so that activity/block indicators work
			// like normal
			if (g && 0 === $.active++) {
				$.event.trigger("ajaxStart");
			}
			if (g) {
				$.event.trigger("ajaxSend", [xhr, s]);
			}

			if (s.beforeSend && s.beforeSend.call(s.context, xhr, s) === false) {
				if (s.global) {
					$.active--;
				}
				deferred.reject();
				return deferred;
			}
			if (xhr.aborted) {
				deferred.reject();
				return deferred;
			}

			// add submitting element to data if we know it
			sub = form.clk;
			if (sub) {
				n = sub.name;
				if (n && !sub.disabled) {
					s.extraData = s.extraData || {};
					s.extraData[n] = sub.value;
					if (sub.type == "image") {
						s.extraData[n + '.x'] = form.clk_x;
						s.extraData[n + '.y'] = form.clk_y;
					}
				}
			}

			var CLIENT_TIMEOUT_ABORT = 1;
			var SERVER_ABORT = 2;

			function getDoc(frame) {
				/*
				 * it looks like contentWindow or contentDocument do not carry
				 * the protocol property in ie8, when running under ssl
				 * frame.document is the only valid response document, since the
				 * protocol is know but not on the other two objects. strange?
				 * "Same origin policy"
				 * http://en.wikipedia.org/wiki/Same_origin_policy
				 */

				var doc = null;

				// IE8 cascading access check
				try {
					if (frame.contentWindow) {
						doc = frame.contentWindow.document;
					}
				} catch (err) {
					// IE8 access denied under ssl & missing protocol
					log('cannot get iframe.contentWindow document: ' + err);
				}

				if (doc) { // successful getting content
					return doc;
				}

				try { // simply checking may throw in ie8 under ssl or
						// mismatched protocol
					doc = frame.contentDocument
							? frame.contentDocument
							: frame.document;
				} catch (err) {
					// last attempt
					log('cannot get iframe.contentDocument: ' + err);
					doc = frame.document;
				}
				return doc;
			}

			// Rails CSRF hack (thanks to Yvan Barthelemy)
			var csrf_token = $('meta[name=csrf-token]').attr('content');
			var csrf_param = $('meta[name=csrf-param]').attr('content');
			if (csrf_param && csrf_token) {
				s.extraData = s.extraData || {};
				s.extraData[csrf_param] = csrf_token;
			}

			// take a breath so that pending repaints get some cpu time before
			// the upload starts
			function doSubmit() {
				// make sure form attrs are set
				var t = $form.attr2('target'), a = $form.attr2('action'), mp = 'multipart/form-data', et = $form
						.attr('enctype')
						|| $form.attr('encoding') || mp;

				// update form attrs in IE friendly way
				form.setAttribute('target', id);
				if (!method || /post/i.test(method)) {
					form.setAttribute('method', 'POST');
				}
				if (a != s.url) {
					form.setAttribute('action', s.url);
				}

				// ie borks in some cases when setting encoding
				if (!s.skipEncodingOverride
						&& (!method || /post/i.test(method))) {
					$form.attr({
								encoding : 'multipart/form-data',
								enctype : 'multipart/form-data'
							});
				}

				// support timout
				if (s.timeout) {
					timeoutHandle = setTimeout(function() {
								timedOut = true;
								cb(CLIENT_TIMEOUT_ABORT);
							}, s.timeout);
				}

				// look for server aborts
				function checkState() {
					try {
						var state = getDoc(io).readyState;
						log('state = ' + state);
						if (state && state.toLowerCase() == 'uninitialized') {
							setTimeout(checkState, 50);
						}
					} catch (e) {
						log('Server abort: ', e, ' (', e.name, ')');
						cb(SERVER_ABORT);
						if (timeoutHandle) {
							clearTimeout(timeoutHandle);
						}
						timeoutHandle = undefined;
					}
				}

				// add "extra" data to form if provided in options
				var extraInputs = [];
				try {
					if (s.extraData) {
						for (var n in s.extraData) {
							if (s.extraData.hasOwnProperty(n)) {
								// if using the $.param format that allows for
								// multiple values with the same name
								if ($.isPlainObject(s.extraData[n])
										&& s.extraData[n]
												.hasOwnProperty('name')
										&& s.extraData[n]
												.hasOwnProperty('value')) {
									extraInputs
											.push(	$('<input type="hidden" name="'
															+ s.extraData[n].name
															+ '">')
															.val(s.extraData[n].value)
															.appendTo(form)[0]);
								} else {
									extraInputs
											.push(	$('<input type="hidden" name="'
															+ n + '">')
															.val(s.extraData[n])
															.appendTo(form)[0]);
								}
							}
						}
					}

					if (!s.iframeTarget) {
						// add iframe to doc and submit the form
						$io.appendTo('body');
					}
					if (io.attachEvent) {
						io.attachEvent('onload', cb);
					} else {
						io.addEventListener('load', cb, false);
					}
					setTimeout(checkState, 15);

					try {
						form.submit();
					} catch (err) {
						// just in case form has element with name/id of
						// 'submit'
						var submitFn = document.createElement('form').submit;
						submitFn.apply(form);
					}
				} finally {
					// reset attrs and remove "extra" input elements
					form.setAttribute('action', a);
					form.setAttribute('enctype', et); // #380
					if (t) {
						form.setAttribute('target', t);
					} else {
						$form.removeAttr('target');
					}
					$(extraInputs).remove();
				}
			}

			if (s.forceSync) {
				doSubmit();
			} else {
				setTimeout(doSubmit, 10); // this lets dom updates render
			}

			var data, doc, domCheckCount = 50, callbackProcessed;

			function cb(e) {
				if (xhr.aborted || callbackProcessed) {
					return;
				}

				doc = getDoc(io);
				if (!doc) {
					log('cannot access response document');
					e = SERVER_ABORT;
				}
				if (e === CLIENT_TIMEOUT_ABORT && xhr) {
					xhr.abort('timeout');
					deferred.reject(xhr, 'timeout');
					return;
				} else if (e == SERVER_ABORT && xhr) {
					xhr.abort('server abort');
					deferred.reject(xhr, 'error', 'server abort');
					return;
				}

				if (!doc || doc.location.href == s.iframeSrc) {
					// response not received yet
					if (!timedOut) {
						return;
					}
				}
				if (io.detachEvent) {
					io.detachEvent('onload', cb);
				} else {
					io.removeEventListener('load', cb, false);
				}

				var status = 'success', errMsg;
				try {
					if (timedOut) {
						throw 'timeout';
					}

					var isXml = s.dataType == 'xml' || doc.XMLDocument
							|| $.isXMLDoc(doc);
					log('isXml=' + isXml);
					if (!isXml && window.opera
							&& (doc.body === null || !doc.body.innerHTML)) {
						if (--domCheckCount) {
							// in some browsers (Opera) the iframe DOM is not
							// always traversable when
							// the onload callback fires, so we loop a bit to
							// accommodate
							log('requeing onLoad callback, DOM not available');
							setTimeout(cb, 250);
							return;
						}
						// let this fall through because server response could
						// be an empty document
						// log('Could not access iframe DOM after mutiple
						// tries.');
						// throw 'DOMException: not available';
					}

					// log('response detected');
					var docRoot = doc.body ? doc.body : doc.documentElement;
					xhr.responseText = docRoot ? docRoot.innerHTML : null;
					xhr.responseXML = doc.XMLDocument ? doc.XMLDocument : doc;
					if (isXml) {
						s.dataType = 'xml';
					}
					xhr.getResponseHeader = function(header) {
						var headers = {
							'content-type' : s.dataType
						};
						return headers[header.toLowerCase()];
					};
					// support for XHR 'status' & 'statusText' emulation :
					if (docRoot) {
						xhr.status = Number(docRoot.getAttribute('status'))
								|| xhr.status;
						xhr.statusText = docRoot.getAttribute('statusText')
								|| xhr.statusText;
					}

					var dt = (s.dataType || '').toLowerCase();
					var scr = /(json|script|text)/.test(dt);
					if (scr || s.textarea) {
						// see if user embedded response in textarea
						var ta = doc.getElementsByTagName('textarea')[0];
						if (ta) {
							xhr.responseText = ta.value;
							// support for XHR 'status' & 'statusText' emulation
							// :
							xhr.status = Number(ta.getAttribute('status'))
									|| xhr.status;
							xhr.statusText = ta.getAttribute('statusText')
									|| xhr.statusText;
						} else if (scr) {
							// account for browsers injecting pre around json
							// response
							var pre = doc.getElementsByTagName('pre')[0];
							var b = doc.getElementsByTagName('body')[0];
							if (pre) {
								xhr.responseText = pre.textContent
										? pre.textContent
										: pre.innerText;
							} else if (b) {
								xhr.responseText = b.textContent
										? b.textContent
										: b.innerText;
							}
						}
					} else if (dt == 'xml' && !xhr.responseXML
							&& xhr.responseText) {
						xhr.responseXML = toXml(xhr.responseText);
					}

					try {
						data = httpData(xhr, dt, s);
					} catch (err) {
						status = 'parsererror';
						xhr.error = errMsg = (err || status);
					}
				} catch (err) {
					log('error caught: ', err);
					status = 'error';
					xhr.error = errMsg = (err || status);
				}

				if (xhr.aborted) {
					log('upload aborted');
					status = null;
				}

				if (xhr.status) { // we've set xhr.status
					status = (xhr.status >= 200 && xhr.status < 300 || xhr.status === 304)
							? 'success'
							: 'error';
				}

				// ordering of these callbacks/triggers is odd, but that's how
				// $.ajax does it
				if (status === 'success') {
					if (s.success) {
						s.success.call(s.context, data, 'success', xhr);
					}
					deferred.resolve(xhr.responseText, 'success', xhr);
					if (g) {
						$.event.trigger("ajaxSuccess", [xhr, s]);
					}
				} else if (status) {
					if (errMsg === undefined) {
						errMsg = xhr.statusText;
					}
					if (s.error) {
						s.error.call(s.context, xhr, status, errMsg);
					}
					deferred.reject(xhr, 'error', errMsg);
					if (g) {
						$.event.trigger("ajaxError", [xhr, s, errMsg]);
					}
				}

				if (g) {
					$.event.trigger("ajaxComplete", [xhr, s]);
				}

				if (g && !--$.active) {
					$.event.trigger("ajaxStop");
				}

				if (s.complete) {
					s.complete.call(s.context, xhr, status);
				}

				callbackProcessed = true;
				if (s.timeout) {
					clearTimeout(timeoutHandle);
				}

				// clean up
				setTimeout(function() {
							if (!s.iframeTarget) {
								$io.remove();
							} else { // adding else to clean up existing
										// iframe response.
								$io.attr('src', s.iframeSrc);
							}
							xhr.responseXML = null;
						}, 100);
			}

			var toXml = $.parseXML || function(s, doc) { // use parseXML if
															// available (jQuery
															// 1.5+)
						if (window.ActiveXObject) {
							doc = new ActiveXObject('Microsoft.XMLDOM');
							doc.async = 'false';
							doc.loadXML(s);
						} else {
							doc = (new DOMParser()).parseFromString(s,
									'text/xml');
						}
						return (doc && doc.documentElement && doc.documentElement.nodeName != 'parsererror')
								? doc
								: null;
					};
			var parseJSON = $.parseJSON || function(s) {
				/* jslint evil:true */
				return window['eval']('(' + s + ')');
			};

			var httpData = function(xhr, type, s) { // mostly lifted from
													// jq1.4.4

				var ct = xhr.getResponseHeader('content-type') || '', xml = type === 'xml'
						|| !type && ct.indexOf('xml') >= 0, data = xml
						? xhr.responseXML
						: xhr.responseText;

				if (xml && data.documentElement.nodeName === 'parsererror') {
					if ($.error) {
						$.error('parsererror');
					}
				}
				if (s && s.dataFilter) {
					data = s.dataFilter(data, type);
				}
				if (typeof data === 'string') {
					if (type === 'json' || !type && ct.indexOf('json') >= 0) {
						data = parseJSON(data);
					} else if (type === "script" || !type
							&& ct.indexOf("javascript") >= 0) {
						$.globalEval(data);
					}
				}
				return data;
			};

			return deferred;
		}
	};

	/**
	 * ajaxForm() provides a mechanism for fully automating form submission.
	 * 
	 * The advantages of using this method instead of ajaxSubmit() are:
	 * 
	 * 1: This method will include coordinates for <input type="image" />
	 * elements (if the element is used to submit the form). 2. This method will
	 * include the submit element's name/value data (for the element that was
	 * used to submit the form). 3. This method binds the submit() method to the
	 * form for you.
	 * 
	 * The options argument for ajaxForm works exactly as it does for
	 * ajaxSubmit. ajaxForm merely passes the options argument along after
	 * properly binding events for submit elements and the form itself.
	 */
	$.fn.ajaxForm = function(options) {
		options = options || {};
		options.delegation = options.delegation && $.isFunction($.fn.on);

		// in jQuery 1.3+ we can fix mistakes with the ready state
		if (!options.delegation && this.length === 0) {
			var o = {
				s : this.selector,
				c : this.context
			};
			if (!$.isReady && o.s) {
				log('DOM not ready, queuing ajaxForm');
				$(function() {
							$(o.s, o.c).ajaxForm(options);
						});
				return this;
			}
			// is your DOM ready?
			// http://docs.jquery.com/Tutorials:Introducing_$(document).ready()
			log('terminating; zero elements found by selector'
					+ ($.isReady ? '' : ' (DOM not ready)'));
			return this;
		}

		if (options.delegation) {
			$(document).off('submit.form-plugin', this.selector, doAjaxSubmit)
					.off('click.form-plugin', this.selector,
							captureSubmittingElement).on('submit.form-plugin',
							this.selector, options, doAjaxSubmit).on(
							'click.form-plugin', this.selector, options,
							captureSubmittingElement);
			return this;
		}

		return this.ajaxFormUnbind().bind('submit.form-plugin', options,
				doAjaxSubmit).bind('click.form-plugin', options,
				captureSubmittingElement);
	};

	// private event handlers
	function doAjaxSubmit(e) {
		/* jshint validthis:true */
		var options = e.data;
		if (!e.isDefaultPrevented()) { // if event has been canceled, don't
										// proceed
			e.preventDefault();
			$(e.target).ajaxSubmit(options); // #365
		}
	}

	function captureSubmittingElement(e) {
		/* jshint validthis:true */
		var target = e.target;
		var $el = $(target);
		if (!($el.is("[type=submit],[type=image]"))) {
			// is this a child element of the submit el? (ex: a span within a
			// button)
			var t = $el.closest('[type=submit]');
			if (t.length === 0) {
				return;
			}
			target = t[0];
		}
		var form = this;
		form.clk = target;
		if (target.type == 'image') {
			if (e.offsetX !== undefined) {
				form.clk_x = e.offsetX;
				form.clk_y = e.offsetY;
			} else if (typeof $.fn.offset == 'function') {
				var offset = $el.offset();
				form.clk_x = e.pageX - offset.left;
				form.clk_y = e.pageY - offset.top;
			} else {
				form.clk_x = e.pageX - target.offsetLeft;
				form.clk_y = e.pageY - target.offsetTop;
			}
		}
		// clear form vars
		setTimeout(function() {
					form.clk = form.clk_x = form.clk_y = null;
				}, 100);
	}

	// ajaxFormUnbind unbinds the event handlers that were bound by ajaxForm
	$.fn.ajaxFormUnbind = function() {
		return this.unbind('submit.form-plugin click.form-plugin');
	};

	/**
	 * formToArray() gathers form element data into an array of objects that can
	 * be passed to any of the following ajax functions: $.get, $.post, or load.
	 * Each object in the array has both a 'name' and 'value' property. An
	 * example of an array for a simple login form might be:
	 *  [ { name: 'username', value: 'jresig' }, { name: 'password', value:
	 * 'secret' } ]
	 * 
	 * It is this array that is passed to pre-submit callback functions provided
	 * to the ajaxSubmit() and ajaxForm() methods.
	 */
	$.fn.formToArray = function(semantic, elements) {
		var a = [];
		if (this.length === 0) {
			return a;
		}

		var form = this[0];
		var formId = this.attr('id');
		var els = semantic ? form.getElementsByTagName('*') : form.elements;
		var els2;

		if (els && !/MSIE [678]/.test(navigator.userAgent)) { // #390
			els = $(els).get(); // convert to standard array
		}

		// #386; account for inputs outside the form which use the 'form'
		// attribute
		if (formId) {
			els2 = $(':input[form="' + formId + '"]').get(); // hat tip @thet
			if (els2.length) {
				els = (els || []).concat(els2);
			}
		}

		if (!els || !els.length) {
			return a;
		}

		var i, j, n, v, el, max, jmax;
		for (i = 0, max = els.length; i < max; i++) {
			el = els[i];
			n = el.name;
			if (!n || el.disabled) {
				continue;
			}

			if (semantic && form.clk && el.type == "image") {
				// handle image inputs on the fly when semantic == true
				if (form.clk == el) {
					a.push({
								name : n,
								value : $(el).val(),
								type : el.type
							});
					a.push({
								name : n + '.x',
								value : form.clk_x
							}, {
								name : n + '.y',
								value : form.clk_y
							});
				}
				continue;
			}

			v = $.fieldValue(el, true);
			if (v && v.constructor == Array) {
				if (elements) {
					elements.push(el);
				}
				for (j = 0, jmax = v.length; j < jmax; j++) {
					a.push({
								name : n,
								value : v[j]
							});
				}
			} else if (feature.fileapi && el.type == 'file') {
				if (elements) {
					elements.push(el);
				}
				var files = el.files;
				if (files.length) {
					for (j = 0; j < files.length; j++) {
						a.push({
									name : n,
									value : files[j],
									type : el.type
								});
					}
				} else {
					// #180
					a.push({
								name : n,
								value : '',
								type : el.type
							});
				}
			} else if (v !== null && typeof v != 'undefined') {
				if (elements) {
					elements.push(el);
				}
				a.push({
							name : n,
							value : v,
							type : el.type,
							required : el.required
						});
			}
		}

		if (!semantic && form.clk) {
			// input type=='image' are not found in elements array! handle it
			// here
			var $input = $(form.clk), input = $input[0];
			n = input.name;
			if (n && !input.disabled && input.type == 'image') {
				a.push({
							name : n,
							value : $input.val()
						});
				a.push({
							name : n + '.x',
							value : form.clk_x
						}, {
							name : n + '.y',
							value : form.clk_y
						});
			}
		}
		return a;
	};

	/**
	 * Serializes form data into a 'submittable' string. This method will return
	 * a string in the format: name1=value1&amp;name2=value2
	 */
	$.fn.formSerialize = function(semantic) {
		// hand off to jQuery.param for proper encoding
		return $.param(this.formToArray(semantic));
	};

	/**
	 * Serializes all field elements in the jQuery object into a query string.
	 * This method will return a string in the format:
	 * name1=value1&amp;name2=value2
	 */
	$.fn.fieldSerialize = function(successful) {
		var a = [];
		this.each(function() {
					var n = this.name;
					if (!n) {
						return;
					}
					var v = $.fieldValue(this, successful);
					if (v && v.constructor == Array) {
						for (var i = 0, max = v.length; i < max; i++) {
							a.push({
										name : n,
										value : v[i]
									});
						}
					} else if (v !== null && typeof v != 'undefined') {
						a.push({
									name : this.name,
									value : v
								});
					}
				});
		// hand off to jQuery.param for proper encoding
		return $.param(a);
	};

	/**
	 * Returns the value(s) of the element in the matched set. For example,
	 * consider the following form:
	 * 
	 * <form><fieldset> <input name="A" type="text" /> <input name="A"
	 * type="text" /> <input name="B" type="checkbox" value="B1" /> <input
	 * name="B" type="checkbox" value="B2"/> <input name="C" type="radio"
	 * value="C1" /> <input name="C" type="radio" value="C2" /> </fieldset></form>
	 * 
	 * var v = $('input[type=text]').fieldValue(); // if no values are entered
	 * into the text inputs v == ['',''] // if values entered into the text
	 * inputs are 'foo' and 'bar' v == ['foo','bar']
	 * 
	 * var v = $('input[type=checkbox]').fieldValue(); // if neither checkbox is
	 * checked v === undefined // if both checkboxes are checked v == ['B1',
	 * 'B2']
	 * 
	 * var v = $('input[type=radio]').fieldValue(); // if neither radio is
	 * checked v === undefined // if first radio is checked v == ['C1']
	 * 
	 * The successful argument controls whether or not the field element must be
	 * 'successful' (per
	 * http://www.w3.org/TR/html4/interact/forms.html#successful-controls). The
	 * default value of the successful argument is true. If this value is false
	 * the value(s) for each element is returned.
	 * 
	 * Note: This method *always* returns an array. If no valid value can be
	 * determined the array will be empty, otherwise it will contain one or more
	 * values.
	 */
	$.fn.fieldValue = function(successful) {
		for (var val = [], i = 0, max = this.length; i < max; i++) {
			var el = this[i];
			var v = $.fieldValue(el, successful);
			if (v === null || typeof v == 'undefined'
					|| (v.constructor == Array && !v.length)) {
				continue;
			}
			if (v.constructor == Array) {
				$.merge(val, v);
			} else {
				val.push(v);
			}
		}
		return val;
	};

	/**
	 * Returns the value of the field element.
	 */
	$.fieldValue = function(el, successful) {
		var n = el.name, t = el.type, tag = el.tagName.toLowerCase();
		if (successful === undefined) {
			successful = true;
		}

		if (successful
				&& (!n || el.disabled || t == 'reset' || t == 'button'
						|| (t == 'checkbox' || t == 'radio') && !el.checked
						|| (t == 'submit' || t == 'image') && el.form
						&& el.form.clk != el || tag == 'select'
						&& el.selectedIndex == -1)) {
			return null;
		}

		if (tag == 'select') {
			var index = el.selectedIndex;
			if (index < 0) {
				return null;
			}
			var a = [], ops = el.options;
			var one = (t == 'select-one');
			var max = (one ? index + 1 : ops.length);
			for (var i = (one ? index : 0); i < max; i++) {
				var op = ops[i];
				if (op.selected) {
					var v = op.value;
					if (!v) { // extra pain for IE...
						v = (op.attributes && op.attributes.value && !(op.attributes.value.specified))
								? op.text
								: op.value;
					}
					if (one) {
						return v;
					}
					a.push(v);
				}
			}
			return a;
		}
		return $(el).val();
	};

	/**
	 * Clears the form data. Takes the following actions on the form's input
	 * fields: - input text fields will have their 'value' property set to the
	 * empty string - select elements will have their 'selectedIndex' property
	 * set to -1 - checkbox and radio inputs will have their 'checked' property
	 * set to false - inputs of type submit, button, reset, and hidden will
	 * *not* be effected - button elements will *not* be effected
	 */
	$.fn.clearForm = function(includeHidden) {
		return this.each(function() {
					$('input,select,textarea', this).clearFields(includeHidden);
				});
	};

	/**
	 * Clears the selected form elements.
	 */
	$.fn.clearFields = $.fn.clearInputs = function(includeHidden) {
		var re = /^(?:color|date|datetime|email|month|number|password|range|search|tel|text|time|url|week)$/i; // 'hidden'
																												// is
																												// not
																												// in
																												// this
																												// list
		return this.each(function() {
					var t = this.type, tag = this.tagName.toLowerCase();
					if (re.test(t) || tag == 'textarea') {
						this.value = '';
					} else if (t == 'checkbox' || t == 'radio') {
						this.checked = false;
					} else if (tag == 'select') {
						this.selectedIndex = -1;
					} else if (t == "file") {
						if (/MSIE/.test(navigator.userAgent)) {
							$(this).replaceWith($(this).clone(true));
						} else {
							$(this).val('');
						}
					} else if (includeHidden) {
						// includeHidden can be the value true, or it can be a
						// selector string
						// indicating a special test; for example:
						// $('#myForm').clearForm('.special:hidden')
						// the above would clean hidden inputs that have the
						// class of 'special'
						if ((includeHidden === true && /hidden/.test(t))
								|| (typeof includeHidden == 'string' && $(this)
										.is(includeHidden))) {
							this.value = '';
						}
					}
				});
	};

	/**
	 * Resets the form data. Causes all form elements to be reset to their
	 * original value.
	 */
	$.fn.resetForm = function() {
		return this.each(function() {
			// guard against an input with the name of 'reset'
			// note that IE reports the reset function as an 'object'
			if (typeof this.reset == 'function'
					|| (typeof this.reset == 'object' && !this.reset.nodeType)) {
				this.reset();
			}
		});
	};

	/**
	 * Enables or disables any matching elements.
	 */
	$.fn.enable = function(b) {
		if (b === undefined) {
			b = true;
		}
		return this.each(function() {
					this.disabled = !b;
				});
	};

	/**
	 * Checks/unchecks any matching checkboxes or radio buttons and
	 * selects/deselects and matching option elements.
	 */
	$.fn.selected = function(select) {
		if (select === undefined) {
			select = true;
		}
		return this.each(function() {
					var t = this.type;
					if (t == 'checkbox' || t == 'radio') {
						this.checked = select;
					} else if (this.tagName.toLowerCase() == 'option') {
						var $sel = $(this).parent('select');
						if (select && $sel[0] && $sel[0].type == 'select-one') {
							// deselect all other options
							$sel.find('option').selected(false);
						}
						this.selected = select;
					}
				});
	};

	// expose debug var
	$.fn.ajaxSubmit.debug = false;

	// helper fn for console logging
	function log() {
		if (!$.fn.ajaxSubmit.debug) {
			return;
		}
		var msg = '[jquery.form] ' + Array.prototype.join.call(arguments, '');
		if (window.console && window.console.log) {
			window.console.log(msg);
		} else if (window.opera && window.opera.postError) {
			window.opera.postError(msg);
		}
	}

}));
